﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;
using System.Security;
using System.Diagnostics;
using CSW.Framework.Common;
using CSW.Framework.Common.Binding;

namespace LinqBusinessObjectBuilder.Options
{
    [Serializable]
    internal class GlobalOptions : BusinessObject, ISerializable
    {
        #region Static Members

        private static readonly GlobalOptions m_GlobalOptions;
        private static readonly string m_OptionsFilePath;
        private static readonly string m_FoundCSWCommonPath;
        private static readonly string m_FoundCSWUIPath;
        private static readonly string m_FoundCSWUIDevExPath;

        static GlobalOptions()
        {
            m_OptionsFilePath = Path.Combine(Program.AppDataPath, @"options.dat");

            m_FoundCSWCommonPath = TryFindCSWPath("CSW.Framework.Common");
            m_FoundCSWUIPath = TryFindCSWPath("CSW.Framework.UI");
            m_FoundCSWUIDevExPath = TryFindCSWPath("CSW.Framework.UI.DevEx");

            m_GlobalOptions = new GlobalOptions();
            m_GlobalOptions.Refresh();
        }

        public static GlobalOptions Instance
        {
            get { return m_GlobalOptions; }
        }

        private static string TryFindCSWPath(string csproj)
        {
            string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

            DirectoryInfo di = new DirectoryInfo(dir);
            bool keepGoing = true;
            do
            {
                DirectoryInfo[] dirs = di.GetDirectories("CSW*");
                if (dirs.Length != 0)
                {
                    foreach (DirectoryInfo testDir in dirs)
                    {
                        if (testDir.Name == csproj)
                        {
                            if (testDir.GetFiles(csproj + ".csproj").Length == 1)
                            {
                                return Path.Combine(testDir.FullName, csproj + ".csproj");
                            }
                        }
                    }
                }

                try
                {
                    di = di.Parent;
                }
                catch (SecurityException ex)
                {
                    keepGoing = false;
                    Trace.WriteLine(ex);
                }
            } while (keepGoing && di != null);

            return null;
        }

        #endregion

        #region Instance Members

        private readonly Property<string> m_SQLMetalPath;
        private readonly Property<string> m_MSBuildPath;
        private readonly Property<string> m_CSWCommonPath;
        private readonly Property<string> m_CSWUIPath;
        private readonly Property<string> m_CSWUIDevExPath;
        private readonly Property<string> m_CurrentProjectPath;
        private readonly List<string> m_RecentProjectList;
        private SerializationInfo m_DeserializationInfo;

        private GlobalOptions()
        {
            string sqlMetalDefaultPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Microsoft SDKs\Windows\v6.0A\bin\sqlmetal.exe");
            string msbuildDefaultPath = Path.Combine(Path.GetDirectoryName(Environment.GetFolderPath(Environment.SpecialFolder.System)), @"Microsoft.NET\Framework\v3.5\msbuild.exe");

            m_SQLMetalPath = new Property<string>(this, "SQLMetalPath", sqlMetalDefaultPath);
            m_MSBuildPath = new Property<string>(this, "MSBuildPath", msbuildDefaultPath);
            m_CSWCommonPath = new Property<string>(this, "CSWCommonPath", m_FoundCSWCommonPath);
            m_CSWUIPath = new Property<string>(this, "CSWUIPath", m_FoundCSWUIPath);
            m_CSWUIDevExPath = new Property<string>(this, "CSWUIDevExPath", m_FoundCSWUIDevExPath);
            m_CurrentProjectPath = new Property<string>(this, "CurrentProjectPath");

            m_RecentProjectList = new List<string>();

            m_CurrentProjectPath.PropertyChanged += m_CurrentProjectPath_PropertyChanged;
        }

        private void m_CurrentProjectPath_PropertyChanged(ComplexProperty<string> sender, BaseProperty<string>.PropertyChangedEventArgs e)
        {
            if (e.OldValue != e.NewValue)
            {
                m_RecentProjectList.Insert(0, e.NewValue);
                List<string> newList = m_RecentProjectList.GroupBy(p => p).Select(p => p.Key).ToList();
                if (newList.Count != m_RecentProjectList.Count)
                {
                    m_RecentProjectList.Clear();
                    m_RecentProjectList.AddRange(newList);
                }
            }
        }

        // Used for serialization.
        private GlobalOptions(SerializationInfo info, StreamingContext context)
            : this()
        {
            Deserialize(info);
        }

        private void Deserialize(SerializationInfo info)
        {
            Guard.ArgumentNotNull(info, "info");

            m_DeserializationInfo = info;

            BeginLoading();
            try
            {
                double version;
                if (!double.TryParse(info.GetString("GlobalOptions/Version"), out version))
                    version = 1.0;

                SQLMetalPath = info.GetString("GlobalOptions/SQLMetalPath");
                if (version >= 1.1)
                {
                    int recentProjectListCount = info.GetInt32("GlobalOptions/RecentProjectListCount");
                    m_RecentProjectList.Clear();
                    for (int i = 0; i < recentProjectListCount; i++)
                    {
                        string file = info.GetString("GlobalOptions/RecentProject" + i);
                        if (File.Exists(file))
                            m_RecentProjectList.Add(file);
                    }

                    CurrentProjectPath = info.GetString("GlobalOptions/CurrentProjectPath");
                }

                if (version >= 1.2)
                {
                    CSWCommonPath = info.GetString("GlobalOptions/CSWCommonPath");
                }

                if (version >= 1.3)
                {
                    MSBuildPath = info.GetString("GlobalOptions/MSBuildPath");
                }

                if (version >= 1.4)
                {
                    CSWUIPath = info.GetString("GlobalOptions/CSWUIPath");
                    CSWUIDevExPath = info.GetString("GlobalOptions/CSWUIDevExPath");
                }
            }
            finally
            {
                EndLoading();
            }
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("GlobalOptions/Version", "1.4");
            info.AddValue("GlobalOptions/SQLMetalPath", SQLMetalPath);
            info.AddValue("GlobalOptions/MSBuildPath", MSBuildPath);
            info.AddValue("GlobalOptions/CurrentProjectPath", CurrentProjectPath);
            info.AddValue("GlobalOptions/CSWCommonPath", CSWCommonPath);
            info.AddValue("GlobalOptions/CSWUIPath", CSWUIPath);
            info.AddValue("GlobalOptions/CSWUIDevExPath", CSWUIDevExPath);

            int projectCount = Math.Min(m_RecentProjectList.Count, 30);
            info.AddValue("GlobalOptions/RecentProjectListCount", projectCount);
            for (int i = 0; i < projectCount; i++)
            {
                info.AddValue("GlobalOptions/RecentProject" + i, m_RecentProjectList[i]);
            }
        }

        public string SQLMetalPath
        {
            get { return m_SQLMetalPath.Value; }
            set { m_SQLMetalPath.Value = value; }
        }

        public string MSBuildPath
        {
            get { return m_MSBuildPath.Value; }
            set { m_MSBuildPath.Value = value; }
        }

        public string CSWCommonPath
        {
            get { return m_CSWCommonPath.Value; }
            set { m_CSWCommonPath.Value = value; }
        }

        public string CSWUIPath
        {
            get { return m_CSWUIPath.Value; }
            set { m_CSWUIPath.Value = value; }
        }

        public string CSWUIDevExPath
        {
            get { return m_CSWUIDevExPath.Value; }
            set { m_CSWUIDevExPath.Value = value; }
        }

        public string CurrentProjectPath
        {
            get { return m_CurrentProjectPath.Value; }
            set { m_CurrentProjectPath.Value = value; }
        }

        public override bool TrySave(out string errorMessage)
        {
            if (!CheckAllRules(out errorMessage))
                return false;
            
            BinaryFormatter formatter = new BinaryFormatter();
            using (FileStream fs = File.Open(m_OptionsFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                formatter.Serialize(fs, m_GlobalOptions);
            }

            IsEditting = false;

            return true;
        }

        public override bool IsNew
        {
            get { return false; }
        }

        public override void Refresh()
        {
            BinaryFormatter formatter = new BinaryFormatter();
            if (File.Exists(m_OptionsFilePath))
            {
                GlobalOptions globalOptions;
                using (FileStream fs = File.Open(m_OptionsFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    globalOptions = (GlobalOptions)formatter.Deserialize(fs);
                }

                m_GlobalOptions.Deserialize(globalOptions.m_DeserializationInfo);
            }
        }

        #endregion
    }
}